在前面的章節中,我們花了許多時間探討 GraphQL 及 Strawberry,這主要是希望讓大家對於 GraphQL 擁有基礎的理解,並對 Strawberry 這個函式庫的功能有所認識。我決定先不將 Django 與 GraphQL 混合在一起,以避免在學習過程中同時需要處理太多的資訊。透過這種方式,大家可以在理解了 GraphQL 的基礎之後,再逐步學習如何結合 Django 進行開發,使學習過程更加順暢與清晰。
接下來,我們將著手建立一個新的 Python 開發環境,並且安裝 Strawberry 與 Django 相關的 Python 套件。我們將示範如何建立一個簡單的 Django 應用程式,實作一個部落格系統。最後,我們將顯示如何將部落格的 ORM 模型,以簡單而直觀的方式轉換成 GraphQL 查詢 API。
首先我們透過 Poetry 建立新的專案開發環境,基本上跟前面的練習環境差不多,如果不清楚的話,可以參考前面 Day 2:安裝 Strawberry 環境 [1]:
$ mkdir django-graphql-tutorial
$ cd django-graphql-tutorial
$ pyenv local 3.11.3
$ poetry init
下面是poetry init
的建立專案的過程,為了方便說明,過程中跳過安裝套件:
This command will guide you through creating your pyproject.toml config.
Package name [django-graphql-tutorial]:
Version [0.1.0]:
Description []:
Author [username <username@email.com>, n to skip]:
License []:
Compatible Python versions [^3.11]:
Would you like to define your main dependencies interactively? (yes/no) [yes] no
Would you like to define your development dependencies interactively? (yes/no) [yes] no
Generated file
[tool.poetry]
name = "django-graphql-tutorial"
version = "0.1.0"
description = ""
authors = ["username <username@email.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Do you confirm generation? (yes/no) [yes]
做完上面的步驟後,當前目錄下,會有以下檔案
.
├── .python-version
└── pyproject.toml
前面的操作,我們使用 Poetry 建立專案,接著就我們的專案的套件管理也交給 Poetry 管理。
$ poetry add django strawberry_graphql_django
執行結果如下:
Using python3 (3.11.3)
Creating virtualenv django-graphql-tutorial-6PNY0bHT-py3.11 in /Users/username/Library/Caches/pypoetry/virtualenvs
Using version ^4.2.5 for django
Using version ^0.17.4 for strawberry-graphql-django
Updating dependencies
Resolving dependencies... (0.7s)
Package operations: 9 installs, 0 updates, 0 removals
• Installing six (1.16.0)
• Installing asgiref (3.7.2)
• Installing graphql-core (3.2.3)
• Installing python-dateutil (2.8.2)
• Installing sqlparse (0.4.4)
• Installing typing-extensions (4.8.0)
• Installing django (4.2.5)
• Installing strawberry-graphql (0.209.2)
• Installing strawberry-graphql-django (0.17.4)
Writing lock file
接下來安裝開發環境所需的套件,這邊先安裝ipython
,在使用 Django Shell 的介面的時候它提供更好地 Python Console 互動介面。
$ poetry add ipython --group dev
Poetry 提供相依套件群組的功能,讓我們針對不同需求所使用的套件進行分組,這個功能讓我們可以在部署應用程式的時候安裝最少需要的套件,這麼做最有感的應該是安裝速度變快與佔用較少的磁碟空間。
再接下來安裝跟型別、格式化與 Lint 等跟程式碼檢查相關的套件:
$ poetry add django-types ruff black pyright --group lint
更多的相關說明可以參考友情連結到 Day03 - 開發輔助工具設定[2] 與 Day04 - 初探 DRF[3]。
再來我們直接使用 Day03 - 開發輔助工具設定 的 ruff 跟 pyright 的設定檔,直接加到pyproject.toml
最後面:
[tool.ruff]
target-version = "py311"
select = [
# pyflakes
"F",
# pycodestyle
"E",
"W",
# pep8-naming
"N",
# pylint
"PL",
# mccabe
"C90",
# isort
"I",
# pydocstyle
"D",
# pyupgrade
"UP",
# flake8-builtins
"A",
# flake8-commas
"COM",
# flake8-bugbear
"B",
# flake8-comprehensions
"C4",
# flake8-type-checking
"TCH",
# flake8-datetimez
"DTZ",
# flake8-print
"T20",
# flake8-tidy-imports
"TID",
# flake8-simplify
"SIM",
# flake8-quotes
"Q",
# flake8-use-pathlib
"PTH",
# flake8-import-conventions
"ICN",
# flake8-django
"DJ",
]
ignore = [
# pydocstyle: Do not require any docstring
"D100",
"D101",
"D102",
"D103",
"D104",
"D105",
"D106",
"D107",
"D212",
"D203",
# pydocstyle: Allow blank line after docstring
"D202",
]
[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "all"
[tool.ruff.pydocstyle]
convention = "google"
[tool.pyright]
pythonVersion = "3.11"
typeCheckingMode = "basic"
reportUnnecessaryTypeIgnoreComment = true
到這邊就完成 Python 專案環境的設定。
使用 django-admin
指令建立 Django 專案:
$ django-admin startproject server .
在當前目錄下.
,建立一個叫server
Django 專案,執行完成後結果如下:
.
├── .python-version
├── manage.py
├── poetry.lock
├── pyproject.toml
└── server
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
Django 專案建立完成後,我通常會建立一個 Python 模組叫app
用來放自己的Django應用程式:
$ mkdir -p server/app/
$ touch server/app/__init__.py
再接下來透過django-admin
指令建立部落格應用程式的模組:
$ cd server/app/
$ django-admin startapp blog
執行完成後,django-graphql-tutorial
資料夾下目錄結構如下:
.
├── .python-version
├── manage.py
├── poetry.lock
├── pyproject.toml
└── server
├── __init__.py
├── app
│ ├── __init__.py
│ └── blog
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
再建立一個utils
模組放共用的程式:
$ cd django-graphql-tutorial
$ mkdir -p server/utils/
$ touch server/utils/__init__.py
接下來我會建立幾個 Django 共用的 Base Model:
$ mkdir -p server/utils/django
$ touch server/utils/django/__init__.py
$ touch server/utils/django/models.py
這邊編輯server/utils/django/models.py
:
import uuid
from django.db import models
__all__ = [
"UUIDModel",
"TimestampModel",
"BaseModel",
]
class UUIDModel(models.Model):
id = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4)
class Meta:
abstract = True
class TimestampModel(models.Model):
created_at = models.DateTimeField("建立時間", auto_now_add=True, editable=False)
motified_at = models.DateTimeField("修改時間", auto_now=True, editable=False)
class Meta:
abstract = True
class BaseModel(UUIDModel, TimestampModel):
class Meta:
abstract = True
UUIDModel
:是將 Django 預設的 Model id 型態改成UUID
。TimestampModel
:增加建立時間跟修改時間的時間戳記欄位。BaseModel
:自己的專案內應用程式用的共用基礎模型。到這邊就完成專案的基本結構。
首先先使用ruff
做檢查:
$ cd django-graphql-tutorial
$ ruff check --fix .
server/settings.py:89:89: E501 Line too long (91 > 88 characters)
server/utils/django/models.py:13:5: A003 Class attribute `id` is shadowing a Python builtin
Found 2 errors.
會看到有兩個錯誤要修正:
# server/settings.py
# ... 省略
AUTH_PASSWORD_VALIDATORS = [
{
- "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", # noqa: E501
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# ... 省略
# server/utils/django/models.py
# ... 省略
class UUIDModel(models.Model):
- id = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4)
+ id = models.UUIDField( # noqa: A003
+ primary_key=True,
+ editable=False,
+ default=uuid.uuid4,
+ )
class Meta:
abstract = True
# ... 省略
然後使用black
自動排版:
$ black .
reformatted /django-graphql-tutorial/server/app/blog/admin.py
reformatted /django-graphql-tutorial/server/app/blog/views.py
reformatted /django-graphql-tutorial/server/app/blog/models.py
reformatted /django-graphql-tutorial/server/app/blog/tests.py
All done! ✨ 🍰 ✨
4 files reformatted, 13 files left unchanged.
最後使用pyright
做型別檢查:
$ pyright .
最終django-graphql-tutorial
目錄結構如下:
.
├── .python-version
├── manage.py
├── poetry.lock
├── pyproject.toml
└── server
├── __init__.py
├── app
│ ├── __init__.py
│ └── blog
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── asgi.py
├── settings.py
├── urls.py
├── utils
│ ├── __init__.py
│ └── django
│ ├── __init__.py
│ └── models.py
└── wsgi.py
完整專案可以參考 Github https://github.com/JiaWeiXie/django-graphql-tutorial.git。